package ch.ethz.fcl.metrobuzz.data.scene.basemap;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.vecmath.Vector2d;

import org.lwjgl.opengl.GL11;

import ch.ethz.fcl.metrobuzz.data.scene.MBScene;
import ch.ethz.fcl.metrobuzz.data.scene.spacetime.Trip.TripSegment;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

public class Edge {
	private MBScene scene;
	private String id;
	private int from;
	private int to;

	// private boolean bidirection;
	private Edge bidirection = null; // record the bidirection edge

	// records the passing trips
	// integer part is the id
	// decimal part is the passing time
	private List<Float> passTrips;

	private byte type = 0;

	// a list of trajectory IDs that pass through sorted by time (0..95 meaning
	// every 15 minutes interval)
	private Map<Integer, List<TripSegment>> passTripMap;

	public Edge(MBScene scene, String id, int from, int to) {
		this.scene = scene;
		this.id = id;

		initEdgeType();

		this.from = from;
		this.to = to;
	}

	public void addPassTrajIds(int time, TripSegment trip) {
		if (passTripMap == null)
			passTripMap = new HashMap<Integer, List<TripSegment>>();

		List<TripSegment> trajList = passTripMap.get(time);
		if (trajList == null) {
			trajList = new ArrayList<TripSegment>();
			trajList.add(trip);
			passTripMap.put(time, trajList);
		} else
			trajList.add(trip);
	}

	public Point2D getCenter(MBScene scene) {
		Node start = scene.getNode(from);
		Node end = scene.getNode(to);

		float centerX = (start.x + end.x) / 2;
		float centerY = (start.y + end.y) / 2;

		return new Point2D(centerX, centerY);
	}

	// this is for OD project
	public List<TripSegment> getPassTrajs(int start, int period) {
		if (start < 0 || start > 96 || period <= 0)
			return null;

		if (passTripMap == null)
			return null;

		List<TripSegment> passTrips = new ArrayList<TripSegment>();
		for (int i = 0; i < period; i++) {
			int time = start + i;
			List<TripSegment> passTripsIdsInTime = passTripMap.get(time);
			if (passTripsIdsInTime != null)
				passTrips.addAll(passTripsIdsInTime);
		}

		return passTrips;
	}

	// this is for interchange project
	public List<Integer> getPassTripIds(float start, float end) {
		List<Integer> passTripsInPeriod = new ArrayList<Integer>();
		for (int i = 0; i < passTrips.size(); i++) {
			int tripId = (int) Math.floor(passTrips.get(i));
			float passTime = passTrips.get(i) - tripId;
			if (passTime >= start && passTime <= end)
				passTripsInPeriod.add(tripId);
		}
		return passTripsInPeriod;
	}

	public String getId() {
		return id;
	}

	public Node getStartNode() {
		return scene.getNode(from);
	}

	public Node getEndNode() {
		return scene.getNode(to);
	}

	public int getStartNodeIndex() {
		return from;
	}

	public int getEndNodeIndex() {
		return to;
	}

	public void update(int from, int end) {
		this.from = from;
		this.to = end;
	}

	/**
	 * edge length on screen
	 * 
	 * @return
	 */
	public float length() {
		Node start = scene.getNode(from);
		Node end = scene.getNode(to);

		return (float) Math.sqrt(Math.pow(end.getX() - start.getX(), 2)
				+ Math.pow(end.getY() - start.getY(), 2));
	}

	/**
	 * real distance (in meters) of the edge
	 * 
	 * @return
	 */
	public float distance() {
		Node start = scene.getNode(from);
		Node end = scene.getNode(to);
		// assume real distance is 1.2 times point-to-point distance
		return scene.getBound().getWorldDist(start.toPoint(), end.toPoint()) * 1.1f;
	}

	public float cos(Edge e) {
		Node e1_start = getStartNode();
		Node e1_end = getEndNode();
		Node e2_start = e.getStartNode();
		Node e2_end = e.getEndNode();

		Vector2d v1 = new Vector2d(e1_end.getX() - e1_start.getX(),
				e1_end.getY() - e1_start.getY());
		Vector2d v2 = new Vector2d(e2_end.getX() - e2_start.getX(),
				e2_end.getY() - e2_start.getY());

		float result = (float) (v1.dot(v2) / (v1.length() * v2.length()));
		return result;
	}

	public void setBidirection(Edge edge) {
		this.bidirection = edge;
	}

	public Vector2d getDir() {
		Node start = scene.getNode(from);
		Node end = scene.getNode(to);
		return new Vector2d(end.x - start.x, end.y - start.y);
	}

	public boolean isParallel(Edge e2) {
		Vector2d dir1 = getDir(), dir2 = e2.getDir();

		double angle = MBTools.angle(dir1, dir2);

		float thre = 0.00001f;
		if (Math.abs(angle) < thre || Math.PI - Math.abs(angle) < thre)
			return true;

		return false;
	}

	public Edge getBidirection() {
		return bidirection;
	}

	@Override
	public String toString() {
		return from + " -> " + to;
	}

	@Override
	public boolean equals(Object o) {
		if (!(o instanceof Edge))
			return false;

		Edge e = (Edge) o;
		if (id.equals(e.id) && from == e.from && to == e.to)
			return true;

		return false;
	}

	@Override
	public int hashCode() {
		return new String(id + from + to).hashCode();
	}

	public void render() {
		Node start = scene.getNode(from);
		Node end = scene.getNode(to);

		GL11.glVertex3f(start.getX(), start.getY(), 0);
		GL11.glVertex3f(end.getX(), end.getY(), 0);
	}

	public String getRouteName() {
		if (type == EdgeType.BUS)
			return "BUS";

		String route = "";
		if ((type & EdgeType.EW) != 0)
			route += "EW";

		if ((type & EdgeType.NS) != 0) {
			if (!route.equals(""))
				route += "/NS";
			else
				route += "NS";
		}

		if ((type & EdgeType.NE) != 0)
			route += "NE";

		if ((type & EdgeType.CC) != 0)
			route += "CC";

		if ((type & EdgeType.LRT) != 0)
			route += "LRT";

		return route;
	}

	private void initEdgeType() {
		if ((this.getId().startsWith("EW") || this.getId().startsWith("CG"))
				&& !this.getId().endsWith("NS2")
				&& !this.getId().endsWith("NS24")
				&& !this.getId().equals("EW14/NS26_NS27")) // East West Line
			type = (byte) (type | EdgeType.EW);
		if (this.getId().startsWith("NS") || this.getId().endsWith("NS2")
				|| this.getId().endsWith("NS23")
				|| this.getId().endsWith("NS16")
				|| this.getId().endsWith("NS18")
				|| this.getId().equals("CC1/NE6/NS24_EW13/NS25")
				|| this.getId().equals("EW13/NS25_CC1/NE6/NS24")
				|| this.getId().equals("EW14/NS26_NS27")
				|| this.getId().equals("EW13/NS25_EW14/NS26")
				|| this.getId().equals("EW14/NS26_EW13/NS25")) // North
																// South
																// line
			type = (byte) (type | EdgeType.NS);
		if (this.getId().startsWith("NE") || this.getId().endsWith("NE10")
				|| this.getId().endsWith("NE13")
				|| this.getId().endsWith("NE5") || this.getId().endsWith("NE7")) // North
																					// East
																					// line
			type = (byte) (type | EdgeType.NE);
		if (this.getId().startsWith("CC") && !this.getId().endsWith("NE10")
				&& !this.getId().endsWith("NE13")
				&& !this.getId().endsWith("NE5")
				&& !this.getId().endsWith("NE7")
				&& !this.getId().endsWith("NS23")
				&& !this.getId().endsWith("NS16")
				&& !this.getId().endsWith("NS18")
				&& !this.getId().equals("CC1/NE6/NS24_EW13/NS25")
				&& !this.getId().equals("CC9/EW8_EW7")
				&& !this.getId().equals("CC9/EW8_EW9")) // Circle
														// line
			type = (byte) (type | EdgeType.CC);
		if (this.getId().startsWith("PE") // LRT
				|| this.getId().startsWith("SE")
				|| this.getId().startsWith("PTC")
				|| this.getId().startsWith("SW"))
			type = (byte) (type | EdgeType.LRT);
		else
			type = (byte) (type | EdgeType.BUS);
	}

	public byte getType() {
		return type;
	}

	public class EdgeType {
		public static final byte BUS = 1;
		public static final byte EW = 2;
		public static final byte NS = 4;
		public static final byte NE = 8;
		public static final byte CC = 16;
		public static final byte LRT = 32;
	}
}
